package com.xiaoshuidi.cloud.module.iot.uitils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.security.rsa.RSAPrivateCrtKeyImpl;
import sun.security.rsa.RSAPublicKeyImpl;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Sky
 * create 2019/08/15
 * email sky.li@ixiaoshuidi.com
 **/
//蚂蚁（ali） IOT 签名工具类
public class AntSignUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(AntSignUtil.class);

    private static final String SHA256_WITH_RSA = "SHA256withRSA";

    /**
     * IoT安全云采用非对称方式进行签名验签，当前算法采用Sha256WithRSA，业务方采用RAS2048方式进行公私钥的生成
     *
     * @return
     * @throws Exception
     */
    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048, new SecureRandom());
        KeyPair pair = generator.generateKeyPair();
        System.out.println("pubkey:" + Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()));
        System.out.println("prikey:" + Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()));
        return pair;
    }

    /**
     * 签名时生成签名原文的算法如下，将请求报文中payload结构中的字段按照字段名字典序升序排列将字段值进行首尾相接,
     * 如果是多层结构则按照递归升序进行排列拼接，拼接完成后在字符串末尾加入时间戳，
     *
     * @param object
     * @param timestamp
     * @return
     * @throws IllegalAccessException
     */
    public static String createSignPlainText(Object object, Long timestamp) throws IllegalAccessException {
        return createSignPlainText(object) + timestamp;
    }

    public static String createSignPlainText(Object object) throws IllegalAccessException {
        if (object == null) {
            return "";
        }
        if (object.getClass().isArray()) {
            return createArraySignPlainText(object);
        } else if (object instanceof Map) {
            return createMapSignPlainText(object);
        } else if (object instanceof Collection) {
            return createCollectionSignPlainText(object);
        } else if (object instanceof Number) {
            return object.toString();
        } else if (object instanceof String) {
            return object.toString();
        } else if (object instanceof Date) {
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return dateFormat.format((Date) object);
        } else if (object instanceof Enum) {
            return ((Enum) object).name();
        } else if (object instanceof Boolean) {
            return object.toString();
        } else {
            return createObjectSignPlainText(object);
        }
    }

    private static String createArraySignPlainText(Object object) throws IllegalAccessException {
        StringBuilder plainText = new StringBuilder();
        int length = Array.getLength(object);
        for (int i = 0; i < length; i++) {
            Object element = Array.get(object, i);
            plainText.append(createSignPlainText(element));
        }
        return plainText.toString();
    }

    private static String createMapSignPlainText(Object mapObject) throws IllegalAccessException {
        StringBuilder plainText = new StringBuilder();
        TreeMap treeMap = new TreeMap<>(String::compareTo);
        treeMap.putAll((Map) mapObject);
        plainText.append(createCollectionSignPlainText(treeMap.values()));
        return plainText.toString();
    }

    private static String createCollectionSignPlainText(Object collection) throws IllegalAccessException {
        StringBuilder plainText = new StringBuilder();
        for (Object object : (Collection) collection) {
            plainText.append(createSignPlainText(object));
        }
        return plainText.toString();
    }

    private static String createObjectSignPlainText(Object object) throws IllegalAccessException {
        Field[] fields = object.getClass().getDeclaredFields();
        List<Field> fieldList = Arrays.stream(fields).sorted(Comparator.comparing(Field::getName)).collect(Collectors.toList());
        StringBuilder plainText = new StringBuilder();
        for (Field field : fieldList) {
            field.setAccessible(true);
            Object value = field.get(object);
            if (value == null) {
                continue;
            }
            plainText.append(createSignPlainText(value));
        }
        return plainText.toString();
    }


    /**
     * 生成签名
     *
     * @param object
     * @param timestamp
     * @param privateKeyBase64
     * @return
     * @throws Exception
     */
    public static String sign(Object object, Long timestamp, String privateKeyBase64) throws Exception {
        String signPlainText = createSignPlainText(object, timestamp);
        LOGGER.info("AntSignUtil,生成签名,plainText:{}", signPlainText);
        return sign(signPlainText, privateKeyBase64);
    }


    /**
     * 生成签名
     *
     * @param plainText
     * @param privateKeyBase64
     * @return
     * @throws Exception
     */
    public static String sign(String plainText, String privateKeyBase64) throws Exception {
        RSAPrivateKey rsaPrivateKey = RSAPrivateCrtKeyImpl.newKey(Base64.getDecoder().decode(privateKeyBase64));
        return sign(plainText, rsaPrivateKey);
    }

    public static String sign(String plainText, PrivateKey privateKey) throws Exception {
        Signature privateSignature = Signature.getInstance(SHA256_WITH_RSA);
        privateSignature.initSign(privateKey);
        privateSignature.update(plainText.getBytes(StandardCharsets.UTF_8));
        byte[] signature = privateSignature.sign();
        return Base64.getEncoder().encodeToString(signature);
    }

    /**
     * 验证签名
     *
     * @param object
     * @param timestamp
     * @param signature
     * @param publicKeyBase64
     * @return
     * @throws Exception
     */
    public static boolean verify(Object object, Long timestamp, String signature, String publicKeyBase64) throws Exception {
        String signPlainText = createSignPlainText(object, timestamp);
        LOGGER.info("AntSignUtil,验证签名,plainText:{}", signPlainText);
        return verify(signPlainText, signature, publicKeyBase64);
    }

    /**
     * 验证签名
     *
     * @param plainText
     * @param signature
     * @param publicKeyBase64
     * @return
     * @throws Exception
     */
    public static boolean verify(String plainText, String signature, String publicKeyBase64) throws Exception {
        RSAPublicKeyImpl publicKey = null;
        boolean verifyResult = verify(plainText, signature, publicKey);
        LOGGER.info("#DSV# method:RSA signature:{} plaintext:{} publicKey:{} result:{}", signature, plainText, publicKeyBase64, verifyResult);
        return verifyResult;
    }

    public static boolean verify(String plainText, String signature, PublicKey publicKey) throws Exception {
        Signature publicSignature = Signature.getInstance(SHA256_WITH_RSA);
        publicSignature.initVerify(publicKey);
        publicSignature.update(plainText.getBytes(StandardCharsets.UTF_8));
        byte[] signatureBytes = Base64.getDecoder().decode(signature);
        return publicSignature.verify(signatureBytes);
    }


    public static void main(String[] args) throws Exception {
        //水滴平台对接蚂蚁IOT，RSA公钥私钥，仅生成一次。
//        generateKeyPair();

        //生成签名测试
        String json = "{\n" +
                "    \"traceId\":\"97eaeade37d546cc80695ac7de1ebd71\",\n" +
                "    \"accessToken\":\"W9CXskaMuAQwld61O0vyRBRyiuRa-8UObOWoIcSQjcE=\",\n" +
                "    \"payload\":\n" +
                "    [\n" +
                "        {\n" +
                "            \"operationType\":\"1900000040004\",\n" +
                "            \"operationAttrValues\":\n" +
                "            {\n" +
                "                \"operationAttrValues\":\n" +
                "                {\n" +
                "                    \"houseInfoList\":\n" +
                "                    [\n" +
                "                        {\n" +
                "                            \"houseId\":\"B20190311\"\n" +
                "                        }\n" +
                "                    ],\n" +
                "                    \"isLazyLoad\":true,\n" +
                "                    \"srcSystemId\":\"FFFFFFFFFFFFFF2\",\n" +
                "                    \"token\":\"TiSPLL-0_4yb-tjJoltHHw9KTCaoIk3JzXlFavkMyZkMBZqA9UjUxhY_Hobp7JYCW4rScQzQsSP7UeE6hXU69Q\",\n" +
                "                }\n" +
                "            }\n" +
                "        }\n" +
                "    ],\n" +
                "    \"timestamp\":1555553652723,\n" +
                "}";
        JSONObject jsonObject = JSONObject.parseObject(json);
        JSONArray payload = jsonObject.getJSONArray("payload");
        String signPlainText = createSignPlainText(payload, System.currentTimeMillis());
        System.out.println("payload签名参数拼装结果:" + signPlainText);
        String prikey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWyNxRk6l6VO38weHgfUSTJYrzCEWkhss8GEIPJm1k4ba4BIMvMkUtjRQhfaBYE8Zym+rkKCR4i+uG1g9QZLQsYkZn2sLNbVpfw32/kMQ32RlJ8OJCokyRMN99sQjApgOUfm2q8bxecz++gGkOB007n7Y0GAHSiBYAJ6TC0yWnUzV42PzPvGfXvl8vfdCwjwYIPLe9pFgYwenQQYDOrzxlsczwH5n2LMvtxoUz0H8NXHlnPeuoEYZK0s20wXPeKUzERU7qLB5utFAv2z1RDrk0Qdw/2wZL/5kHhPHY6NiEIIMpGA5cXybxEleIYiMnU1ZFm9IuPJh6EjLVjNMkOPhrAgMBAAECggEASPmxV3HlpiUQgTG8hg9abvTOiMzKF9R86/q5jeeFLjvnWKwmaWMlA+Sxh3vrbHU1qCY5ym57KDRfqwRSqQ/z+z8c4AqKGS4BfTN52orPzE+v449HkgRGlkZXA9f4anRuayG9JeyUxAl3IlW3gmiaQm8Lma68ww3FyBFntjrGkHH64oB77ZabwowmfYMhxpjls7e8WHuabLgalJ8thSovFWBrxIVEeeOTW1kkJLai+Z4CfGG1V/i18PndCHL0Ehlh5L/7smt0SI90YuERC7wpiepZz0AOhmZpAAmsYcbqF0yVWck/rb+xzfQudVqT1hfI3Ke+keXjdsBmIz5Ze7mJsQKBgQD8HgI04/WWA+/+DxNOwFTJUNhWBrCnYwYralpUtTk33gL+W+O4BFl1lKS9yom3al1vYh/DsmcxJFFa8Lbn3UYDW4FvIXG6GvOZx02Ju48vNjC72YkmLjvRwb+g2L3Sw3vUhlp6vbpctPV04xdTQn/SqPwc9QOnAgXbW/wad/5V7QKBgQCZG1coJPbdPytFCrUv+F+3kfJWDuU0Dos1qtUXf8oFHVuZvONNUEYAdsPeD1ubGSIp8jrTsxxD7d0zZ6jAv3OZO0scP+3pjI86mDnb9yYqTPBYB3Pv+vyewHqO6R9ijXeG7Ds7dXQoL4F/JgjUPzGRO3mR7F2PG+jKDjMCfZ48twKBgE5GFtNK7wVbRZhwH8N0bR5vjirvOstNMC4sf80K0q/oqq9D4ihQmWGq+5a9GhKq4EVjho8ghW85YlTEpVTJWyJRt5bEIGXyVi0pNoaLh8HVpj28JRuIgSP3bkJG6HBWJsztxYuT+hcsS4S1uJkt7Td/JX7sBu1D8IORRG0HUhxlAoGBAI0Z41L97fyssMXqPl09O4lEGeAxzg4RsT3+eHA1twrLqZWfvKON7VWUHqLR6IyhtxiNQqXDlbMMziUHzUAFsRVwzaH1UN9CSOWpUPllZmjpiz61b6Wk25e6r7hxJBmBYryXjWcYsbovPdreRJjGvHmqJDnVacvv3rQILf6HooM1AoGAFJmR0RvlWv4FjkrV3fwqBA/PLr+jf5+Einbuv5Lo2aV5AWgoKL4qjBcoYU5TK8Q0PFyZos+TlEs+wnZ0z3IONQwig4piJuA9uQRMOXcODnQrLoXNc9s2In7lBFULwm0r7jmDMgftmdS0GVixPKGBSDBGfn0K+KuDWp8W/CBiwyI=";
        String pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlsjcUZOpelTt/MHh4H1EkyWK8whFpIbLPBhCDyZtZOG2uASDLzJFLY0UIX2gWBPGcpvq5CgkeIvrhtYPUGS0LGJGZ9rCzW1aX8N9v5DEN9kZSfDiQqJMkTDffbEIwKYDlH5tqvG8XnM/voBpDgdNO5+2NBgB0ogWACekwtMlp1M1eNj8z7xn175fL33QsI8GCDy3vaRYGMHp0EGAzq88ZbHM8B+Z9izL7caFM9B/DVx5Zz3rqBGGStLNtMFz3ilMxEVO6iwebrRQL9s9UQ65NEHcP9sGS/+ZB4Tx2OjYhCCDKRgOXF8m8RJXiGIjJ1NWRZvSLjyYehIy1YzTJDj4awIDAQAB";
        //私钥加签名
        String sign = sign(signPlainText, prikey);
        System.out.println("私钥加签结果：" + sign);
        //公钥验签
        boolean verify = verify(signPlainText, sign, pubKey);
        System.out.println("公钥验签结果：" + verify);
    }
}
